import Dep from "./observe/dep";
import { observe } from "./observe/index";
import Watcher, { nextTick } from "./observe/watcher";

export function initState(vm) {
  // 获取所有选项
  const opts = vm.$options;

  // 如果有data数据，则初始化data数据
  if (opts.data) {
    initData(vm);
  }

  // 如果有计算属性，则初始化计算属性
  if (opts.computed) {
    initComputed(vm);
  }

  // 如果有侦听器，则初始化侦听器
  if (opts.watch) {
    initWatch(vm);
  }
}

// 代理
function proxy(vm, target, key) {
  Object.defineProperty(vm, key, {
    get() {
      return vm[target][key]; // vm._data.xxx
    },
    set(newValue) {
      vm[target][key] = newValue;
    },
  });
}

function initData(vm) {
  // 获取所有data数据
  let data = vm.$options.data;

  // Vue2中data可以是对象也可以是函数（Vue3统一函数），因此需要先判断。这里的data是用户的数据
  data = typeof data === "function" ? data.call(vm) : data;

  // 此时vm只有用户的数据，没有我们劫持后的数据。把劫持后的数据放到原型上供用户使用。这里的_data是劫持后的对象
  vm._data = data;

  // 劫持数据 defindProperty
  observe(data);

  // 此时用户想要获取或者修改数据，必须通过 vm._data.xxx 的写法，不够人性化。把 vm._data 用 vm 来代理
  for (const key in data) {
    proxy(vm, "_data", key);
  }
}

function initComputed(vm) {
  const computed = vm.$options.computed;
  // 吧计算属性watcher保存到vm上
  const watchers = (vm._computedWatchers = {});

  // 循环computed对象，拿到每一个计算属性
  for (const key in computed) {
    let userDef = computed[key];

    // 监控计算属性get的变化
    let fn = typeof userDef === "function" ? userDef : userDef.get;

    // 如果直接 new Watcher，就会直接执行fn，但是我们不希望他立即执行，而是懒执行
    watchers[key] = new Watcher(vm, fn, { lazy: true });

    defineComputed(vm, key, userDef);
  }
}

function defineComputed(target, key, userDef) {
  // const getter = typeof userDef === "function" ? userDef : userDef.get;
  const setter = userDef.set || (() => {});

  // 通过实例拿到对应的属性
  Object.defineProperty(target, key, {
    get: createComputedGetter(key),
    set: setter,
  });
}

// 计算属性根本不会收集依赖，只会让自己的依赖属性去收集依赖
function createComputedGetter(key) {
  // 检测是否要执行这个getter
  return function () {
    // 获取到对应属性的watcher
    const watcher = this._computedWatchers[key];
    if (watcher.dirty) {
      // 如果是脏的就去执行用户传入的函数。求值后dirty变为false，下次就不求值了
      watcher.evaluate();
    }
    if (Dep.target) {
      // 计算属性出栈后 还要渲染watcher 应该让计算属性watcher里的属性也收集上一层watcher
      watcher.depend();
    }
    return watcher.value;
  };
}

function initWatch(vm) {
  let watch = vm.$options.watch;

  for (const key in watch) {
    // 拿到值来判断是哪种情况：字符串、数组、函数
    const handler = watch[key];

    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i]);
      }
    } else {
      createWatcher(vm, key, handler);
    }
  }
}

// 侦听器处理
function createWatcher(vm, key, handler) {
  if (typeof handler === "string") {
    // 如果是字符串，写法为 watch: {name: 'fn'} 此时函数在methods内，直接拿过来用
    handler = vm[handler];
  }

  // 最后都是走$watch方法
  return vm.$watch(key, handler);
}

export function initStateMixin(Vue) {
  Vue.prototype.$nextTick = nextTick;
  // 侦听器最终调用的都是这个方法
  Vue.prototype.$watch = function (exprOrFn, cb) {
    console.log(exprOrFn, cb);
    // exprOrFn可能是一个函数，如()=>vm.nam；也可能是字符串，如name

    // {user：true} 表示这是用户写的

    // 调用Watcher类表示值发生了变化，调用cb函数即可
    new Watcher(this, exprOrFn, { user: true }, cb);
  };
}
